# LuckPay Web Manage System
#
# Copyright (c) 2016 Lucky Byte, Inc.
#
express = require 'express'
pgsql   = require 'pg'
uuid    = require 'uuid'
moment  = require 'moment'
fs      = require 'fs'
multer  = require 'multer'
mmm     = require 'mmmagic'
mkdirp  = require 'mkdirp'
crypto  = require 'crypto'
path    = require 'path'
jobs    = require '../lib/terms_jobs'
secure  = require '../lib/secure'
router = express.Router()
module.exports = router


# 下面这些字段不能在数据表中存入空字符串(外键或格式要求)
router.use (req, res, next) ->
    for k in [
        'merid', 'mapto_term', 'tmk',
        'input_time', 'check_time', 'effect_time' ]
        if not req.body[k] or req.body[k].length == 0
            req.body[k] = null
    next()


router.all '/', (req, res, next) ->
    where_clause = "(
        t.serial::text like $1 or t.uuid like $1 or
        t.termid like $1 or t.tmk like $1 or t.notes like $1 or
        m.merno like $1 or m.name like $1
    )"
    # 通过 inside 过滤(如果提供的话)
    if req.body.inside and req.body.inside.length > 0
        where_clause += " and m.inside = #{req.body.inside is 'true'}"
        res.locals.inside = req.body.inside

    # 通过渠道过滤(如果提供的话)
    if req.body.chnl and req.body.chnl.length > 0
        where_clause += " and m.chnl = #{req.body.chnl}"
        res.locals.chnl = req.body.chnl

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    # 查询终端表中的记录总数
    await client.query \
        "select count(t.uuid)::int as count
            from pay_terms as t
            left join pay_merch as m on m.uuid = t.merid
            where #{where_clause} and t.shaded = false", [
                '%' + res.locals.keyword + '%'
            ], defer err, result
    if err then done(client); return next(err)
    res.locals.total = result.rows[0].count

    # 非正常(复核表中)终端数量，计算偏移时需要使用
    ex_count = 0

    # 查询待复核表中当前用户录入的记录
    await client.query \
        "select count(t.uuid)::int as count
            from pay_terms_ex as t
            left join pay_merch as m on m.uuid = t.merid
            where #{where_clause} and t.inputer = $2", [
                '%' + res.locals.keyword + '%'
                req.session.userinfo.uuid,
            ], defer err, result
    if err then done(client); return next(err)
    ex_count += result.rows[0].count

    # 如果用户具有复核权限，那么查询待复核的记录
    if res.locals.page_perms.check
        await client.query \
            "select count(t.uuid)::int as count
                from pay_terms_ex as t
                left join pay_merch as m on m.uuid = t.merid
                where #{where_clause} and t.inputer <> $2 and
                    t.checked = false and t.rejected = false", [
                '%' + res.locals.keyword + '%'
                req.session.userinfo.uuid
            ], defer err, result
        if err then done(client); return next(err)
        ex_count += result.rows[0].count

    # 由当前用户复核后尚未生效的记录
    if res.locals.page_perms.check
        await client.query \
            "select count(t.uuid)::int as count
                from pay_terms_ex as t
                left join pay_merch as m on m.uuid = t.merid
                where #{where_clause} and
                    t.checker = $2 and t.checked = true", [
                '%' + res.locals.keyword + '%'
                req.session.userinfo.uuid
            ], defer err, result
        if err then done(client); return next(err)
        ex_count += result.rows[0].count

    res.locals.total += ex_count
    ret_records = []

    # 首先查询待复核的记录，置顶，仅出现在第一页
    if res.locals.offset == 0 and res.locals.page_perms.check
        await client.query \
            "select t.*, u.realname as input_user,
                m.name as mer_name, m.merno as mer_no,
                m.inside as mer_inside, m.chnl as mer_chnl,
                t2.termid as mapto_term_id,
                true as ex
                from pay_terms_ex as t
                left join web_users as u on u.uuid = t.inputer
                left join pay_merch as m on m.uuid = t.merid
                left join pay_terms as t2 on t2.uuid = t.mapto_term
                where #{where_clause} and t.inputer <> $2 and
                    t.checked = false and t.rejected = false
                order by t.serial desc limit $3::int", [
                '%' + res.locals.keyword + '%'
                req.session.userinfo.uuid
                res.locals.page_rows - ret_records.length
            ], defer err, result
        if err then done(client); return next(err)
        ret_records.push result.rows...

    # 如果查询的结果集还不足一页，则继续查找复核后未生效的记录
    if (
        res.locals.offset == 0 and res.locals.page_perms.check and
        ret_records.length < res.locals.page_rows)
        await client.query \
            "select t.*, u.realname as input_user,
                u2.realname as check_user,
                m.name as mer_name, m.merno as mer_no,
                m.inside as mer_inside, m.chnl as mer_chnl,
                t2.termid as mapto_term_id,
                true as ex
                from pay_terms_ex as t
                left join web_users as u on u.uuid = t.inputer
                left join web_users as u2 on u2.uuid = t.checker
                left join pay_merch as m on m.uuid = t.merid
                left join pay_terms as t2 on t2.uuid = t.mapto_term
                where #{where_clause} and t.checker = $2 and t.checked = true
                order by t.serial desc limit $3::int", [
                '%' + res.locals.keyword + '%'
                req.session.userinfo.uuid
                res.locals.page_rows - ret_records.length
            ], defer err, result
        if err then done(client); return next(err)
        ret_records.push result.rows...

    # 如果查询的结果集还不足一页，则继续查找用户录入或修改的记录
    if res.locals.offset == 0 and ret_records.length < res.locals.page_rows
        await client.query \
            "select t.*, u.realname as input_user,
                u2.realname as check_user,
                m.name as mer_name, m.merno as mer_no,
                m.inside as mer_inside, m.chnl as mer_chnl,
                t2.termid as mapto_term_id,
                true as ex
                from pay_terms_ex as t
                left join web_users as u on u.uuid = t.inputer
                left join web_users as u2 on u2.uuid = t.checker
                left join pay_merch as m on m.uuid = t.merid
                left join pay_terms as t2 on t2.uuid = t.mapto_term
                where #{where_clause} and t.inputer = $2
                order by t.serial desc limit $3::int", [
                '%' + res.locals.keyword + '%'
                req.session.userinfo.uuid
                res.locals.page_rows - ret_records.length
            ], defer err, result
        if err then done(client); return next(err)
        ret_records.push result.rows...

    # 如果查询的结果集还不足一页，则继续查找正常的记录
    if ret_records.length < res.locals.page_rows
        await client.query \
            "select t.*, u.realname as input_user,
                u2.realname as check_user,
                m.name as mer_name, m.merno as mer_no,
                m.inside as mer_inside, m.chnl as mer_chnl,
                m.disabled as mer_disabled,
                m.shaded as mer_shaded,
                t2.termid as mapto_term_id
                from pay_terms as t
                left join web_users as u on u.uuid = t.inputer
                left join web_users as u2 on u2.uuid = t.checker
                left join pay_merch as m on m.uuid = t.merid
                left join pay_terms as t2 on t2.uuid = t.mapto_term
                where #{ where_clause } and t.shaded = false
                order by t.serial desc
                offset $2::int limit $3::int", [
                    '%' + res.locals.keyword + '%'
                    if res.locals.offset == 0 then 0 else res.locals.offset - ex_count
                    res.locals.page_rows - ret_records.length
                ], defer err, result
        if err then done(client); return next(err)
        ret_records.push result.rows...

    done()
    res.render 'terms/index', records: ret_records


# 动态获取编辑页面中下拉框数据
body_dropdown_items = (req, res, next) ->
    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    # 删除时，可以查询被禁用的商户，如果不这样，前端 form 验证将报告商户不能为空
    if req.path.startsWith('/check/3')
        where_clause = '1 = 1'
    else
        where_clause = 'disabled = false'

    # 查询隶属商户列表
    await client.query \
        "select * from pay_merch where #{where_clause} and chnl in (1, 4)
            order by ctime desc, merno",
        defer err, result
    if err then done(client); return next(err)
    res.locals.merids = result.rows
    done()
    next()


router.get '/new', body_dropdown_items, (req, res, next) ->
    res.locals.breadcrumbs.push 'title': '新增终端'
    res.render 'terms/new', record: {}


# 修改数据库记录前检查
update_precheck = (req, res, next) ->
    for k in ['merid', 'termid']
        return next(new Error("请求缺少参数[#{k}]")) if not req.body[k]

    # 去除终端号空白字符
    req.body.termid = req.body.termid.replace(/\s/g, '')

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query "select * from pay_merch where uuid = $1", [
            req.body.merid
        ], defer err, result
    if err then done(client); return next(err)
    done()

    if result.rows.length == 0
        return next new Error("隶属商户 #{req.body.merid} 不存在，请检查")
    merch = result.rows[0]

    if merch.inside and not req.body.tmk
        return next new Error("平台终端需要配置终端主密钥")

    # 如果 Tmk 改变了，则需要加密
    if req.body.tmk and req.body.tmk != req.body.tmk_old
        req.body.tmk = secure.encryptEde req.body.tmk
        if not req.body.tmk
            return next new Error('加密终端主密钥失败')

    # 更新终端信息可能影响到所有用户的通知
    # 更新系统的通知修改时间，用户下次访问时，将重新查询通知消息
    req.app.set 'notify_modified_time', moment().format('x')
    next()


router.post '/new', update_precheck, (req, res, next) ->
    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    # 查询终端是否已经存在
    await client.query \
        "select * from pay_terms where merid = $1 and termid = $2", [
            req.body.merid, req.body.termid
        ], defer err, result
    if err then done(client); return next(err)
    if result.rows.length > 0
        done()
        return next new Error("""
            商户 #{req.body.merid} 下的终端 #{req.body.termid} 已存在，请检查""")

    # 查询待复核表中是否有此终端信息
    await client.query \
        "select t.*, u.realname as input_user
            from pay_terms_ex as t
            left join web_users as u on u.uuid = t.inputer
            where t.merid = $1 and t.termid = $2 and t.action = 1", [
            req.body.merid, req.body.termid
        ], defer err, result
    if err then done(client); return next(err)
    if result.rows.length > 0
        done()
        return next new Error("终端 #{req.body.termid} 已被用户 " +
            "#{result.rows[0].input_user} 添加，尚未复核或尚未生效")

    # 添加一条影子记录
    term_uuid = uuid.v4()
    await client.query "insert into pay_terms (
            uuid, termid, merid, inputer, checker, shaded
        ) values (
            $1, $2, $3, $4, $5, true
        )", [
            term_uuid, req.body.termid, req.body.merid,
            req.session.userinfo.uuid,
            req.session.userinfo.uuid
        ], defer err, result
    if err then done(client); return next(err)

    # 添加商户信息到待复核表
    await client.query "insert into pay_terms_ex (
            uuid,       termid,     merid,
            mapto_term, tmk,        macalgo,
            notes,      ref_uuid,
            inputer,    input_time,     action
        ) values (
            $1, $2, $3,
            $4, $5, $6,
            $7, $8,
            $9, CURRENT_TIMESTAMP, 1
        )", [
            uuid.v4(),
            req.body.termid,
            req.body.merid,
            req.body.mapto_term,
            req.body.tmk,
            parseInt(req.body.macalgo) or 0,
            req.body.notes,
            term_uuid,
            req.session.userinfo.uuid
        ], defer err, result
    if err then done(client); return next(err)
    done()
    res.redirect '/terms'


router.get '/edit/:id', body_dropdown_items, (req, res, next) ->
    res.locals.breadcrumbs.push 'title': '编辑终端信息'

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query \
        "select * from pay_terms
            where serial = $1::int or uuid = $2", [
            parseInt(req.params.id) or 0, req.params.id
        ], defer err, result
    if err then done(client); return next(err)
    done()

    if result.rows.length == 0
        return next new Error("未查询到终端 #{req.params.id} 的信息")

    record = result.rows[0]
    res.render 'terms/edit', record: record


router.post '/edit', update_precheck, (req, res, next) ->
    for k in ['serial', 'uuid']
        return next(new Error("请求缺少参数[#{k}]")) if not req.body[k]

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query \
        "select * from pay_terms_ex where ref_uuid = $1", [
            req.body.uuid
        ], defer err, result
    if err then done(client); return next(err)
    if result.rows.length > 0
        done()
        return next new Error('该记录被用户修改后尚未生效，在新信息生效前不允许再次修改')

    await client.query "insert into pay_terms_ex (
            uuid,       termid, merid,
            mapto_term, tmk,    macalgo,
            notes,
            ref_uuid,          ref_serial,
            inputer,           input_time,     action
        ) values (
            $1, $2, $3,
            $4, $5, $6,
            $7,
            $8, $9,
            $10, CURRENT_TIMESTAMP, 2
        )", [
            uuid.v4(),
            req.body.termid,
            req.body.merid,
            req.body.mapto_term,
            req.body.tmk,
            parseInt(req.body.macalgo) or 0,
            req.body.notes,
            req.body.uuid,
            req.body.serial,
            req.session.userinfo.uuid,
        ], defer err, result
    if err then done(client); return next(err)
    done()
    res.redirect '/terms'


router.post '/disable', (req, res, next) ->
    res.type('json')
    for k in ['serial', 'uuid', 'disabled']
        return next(new Error("请求缺少参数[#{k}]")) if not req.body[k]

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query \
        "update pay_terms set disabled = $1
            where serial = $2 and uuid = $3", [
            req.body.disabled, req.body.serial, req.body.uuid,
        ], defer err, result
    if err then done(client); return next(err)
    done()
    res.json succ: true, redirect: '/terms'


router.post '/delete', (req, res, next) ->
    res.type('json')
    for k in ['serial', 'uuid']
        return next(new Error("请求缺少参数[#{k}]")) if not req.body[k]

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    # 如果此记录存在待复核的条目，则禁止删除
    await client.query \
        "select * from pay_terms_ex where ref_uuid = $1", [
            req.body.uuid
        ], defer err, result
    if err then done(client); return next(err)
    if result.rows.length > 0
        done()
        return next new Error('该记录被用户修改后尚未生效，在新信息生效前不允许删除')

    await client.query "insert into pay_terms_ex (
            uuid,           ctime,
            termid,         merid,
            mapto_term,
            tmk,            macalgo,
            notes,
            ref_uuid,       ref_serial,
            inputer,        input_time,     action
        ) ( select
            $1,             ctime,
            termid,         merid,
            mapto_term,
            tmk,            macalgo,
            notes,
            uuid,           serial,
            $2,             CURRENT_TIMESTAMP, 3
            from pay_terms where uuid = $3 and serial = $4
        )", [
            uuid.v4(),
            req.session.userinfo.uuid,
            req.body.uuid, req.body.serial
        ], defer err, result
    if err then done(client); return next(err)
    done()

    # 更新系统的通知修改时间，用户下次访问时，将重新查询通知消息
    req.app.set 'notify_modified_time', moment().format('x')
    res.json succ: true, redirect: '/terms'


router.get '/input/:id', body_dropdown_items, (req, res, next) ->
    res.locals.breadcrumbs.push 'title': '修改终端信息'

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query \
        "select t.*, u.realname as check_user
            from pay_terms_ex as t
            left join web_users as u on u.uuid = t.checker
            where (t.serial = $1::int or t.uuid = $2) and t.inputer = $3", [
            parseInt(req.params.id) or 0, req.params.id,
            req.session.userinfo.uuid
        ], defer err, result
    if err then done(client); return next(err)
    done()

    if result.rows.length == 0
        return next new Error("未查询到终端 #{req.params.id} 的信息")

    record = result.rows[0]
    if record.checked
        return next new Error("""
            该记录已被用户 #{record.check_user} 复核，不允许继续修改""")

    res.render 'terms/input', record: record


router.post '/input/delete', (req, res, next) ->
    res.type('json')
    for k in ['serial', 'uuid']
        return next(new Error("请求缺少参数[#{k}]")) if not req.body[k]

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query \
        "select * from pay_terms_ex where
            inputer = $1 and checked = false and
            serial = $2 and uuid = $3", [
            req.session.userinfo.uuid, req.body.serial, req.body.uuid,
        ], defer err, result
    if err then done(client); return next(err)
    if result.rows.length == 0
        done()
        return next new Error('未查询到待删除记录，可能已被删除.')

    remove_record = result.rows[0]

    client.query 'begin'
    await client.query \
        "delete from pay_terms_ex where
            inputer = $1 and checked = false and
            serial = $2 and uuid = $3", [
            req.session.userinfo.uuid, req.body.serial, req.body.uuid,
        ], defer err, result
    if err then done(client); return next(err)

    # 如果是删除的是新增未复核记录，则商户表中存在一条影子记录，需要删除
    if remove_record.action == 1
        await client.query \
            "delete from pay_terms where shaded = true and uuid = $1", [
                remove_record.ref_uuid
            ], defer err, result
        if err then done(client); return next(err)
    client.query 'commit'
    done()

    # 更新系统的通知修改时间，用户下次访问时，将重新查询通知消息
    req.app.set 'notify_modified_time', moment().format('x')
    res.json succ: true, redirect: '/terms'


input_precheck = (req, res, next) ->
    for k in ['serial', 'uuid']
        if not req.body[k]
            return next new Error("请求缺少参数[#{k}]")

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    # 检查记录是否已被复核，复核后的信息不允许修改
    await client.query \
        "select t.checked, u.realname as check_user
            from pay_terms_ex as t
            left join web_users as u on u.uuid = t.checker
            where t.serial = $1 and t.uuid = $2", [
            req.body.serial, req.body.uuid
        ], defer err, result
    if err then done(client); return next(err)
    done()

    if result.rows.length == 0
        return next new Error("""
            未找到终端 #{req.body.uuid} 的信息，可能已被复核并已生效""")

    record = result.rows[0]
    if result.rows[0].checked
        return next new Error("""
            该终端信息已被用户 #{record.check_user} 复核，不允许继续修改""")
    next()


do_input_1 = (req, res, next) ->
    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query "update pay_terms_ex set
            termid = $1,         merid = $2,
            mapto_term = $3,
            tmk = $4,            macalgo = $5,
            notes = $6,
            inputer = $7,        input_time = CURRENT_TIMESTAMP,
            checked = false,     rejected = false
        where serial = $8 and uuid = $9", [
            req.body.termid,
            req.body.merid,
            req.body.mapto_term,
            req.body.tmk,
            parseInt(req.body.macalgo) or 0,
            req.body.notes,
            req.session.userinfo.uuid,
            req.body.serial, req.body.uuid
        ], defer err, result
    if err then done(client); return next(err)
    done()


do_input_3 = (req, res, next) ->
    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query "update pay_terms_ex set
            inputer = $1,        input_time = CURRENT_TIMESTAMP,
            checked = false,     rejected = false
        where serial = $2 and uuid = $3", [
            req.session.userinfo.uuid,
            req.body.serial, req.body.uuid
        ], defer err, result
    if err then done(client); return next(err)
    done()


router.post '/input/:action', update_precheck, input_precheck, (req, res, next) ->
    switch parseInt(req.params.action)
        when 1, 2 then do_input_1 req, res, next
        when 3    then do_input_3 req, res, next
        else next new Error('您访问的功能不存在')

    res.redirect '/terms'


router.get '/check/1/:id', body_dropdown_items, (req, res, next) ->
    res.locals.breadcrumbs.push 'title': '确认新增终端'

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query "select t.*, u.realname as input_user,
                u2.realname as check_user
            from pay_terms_ex as t
            left join web_users as u on t.inputer = u.uuid
            left join web_users as u2 on t.checker = u2.uuid
            where (t.serial = $1 or t.uuid = $2) and
                t.inputer <> $3 and action = 1", [
        parseInt(req.params.id) or 0, req.params.id,
        req.session.userinfo.uuid
    ], defer err, result
    if err then done(client); return next(err)
    done()

    if result.rows.length == 0
        return next(new Error('未找到待复核终端记录'))
    record = result.rows[0]
    res.render 'terms/check1', record: record


router.get '/check/2/:id', (req, res, next) ->
    res.locals.breadcrumbs.push 'title': '确认变更终端信息'

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query "select t.*, u.realname as input_user,
                u2.realname as check_user,
                m.name as mer_name, m.merno as mer_no,
                m.inside as mer_inside, m.chnl as mer_chnl,
                t2.termid as mapto_term_id
            from pay_terms_ex as t
            left join web_users as u on t.inputer = u.uuid
            left join web_users as u2 on t.checker = u2.uuid
            left join pay_merch as m on t.merid = m.uuid
            left join pay_terms as t2 on t.mapto_term = t2.uuid
            where (t.serial = $1 or t.uuid = $2) and
                t.inputer <> $3 and action = 2", [
            parseInt(req.params.id) or 0, req.params.id,
            req.session.userinfo.uuid
        ], defer err, result
    if err then done(client); return next(err)

    if result.rows.length == 0
        done()
        return next(new Error('未找到终端记录'))

    record = result.rows[0]

    # 查询原记录信息
    await client.query "select t.*,
                m.name as mer_name, m.merno as mer_no,
                m.inside as mer_inside, m.chnl as mer_chnl,
                t2.termid as mapto_term_id
            from pay_terms as t
            left join pay_merch as m on t.merid = m.uuid
            left join pay_terms as t2 on t.mapto_term = t2.uuid
            where t.uuid = $1", [
            record.ref_uuid
        ], defer err, result
    if err then done(client); return next(err)
    done()

    if result.rows.length == 0
        return next(new Error('未找到原终端记录'))
    res.locals.ref_record = result.rows[0]

    res.render 'terms/check2', record: record


router.get '/check/3/:id', body_dropdown_items, (req, res, next) ->
    res.locals.breadcrumbs.push 'title': '确认删除终端'

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query "select t.*, u.realname as input_user,
                u2.realname as check_user
            from pay_terms_ex as t
            left join web_users as u on t.inputer = u.uuid
            left join web_users as u2 on t.checker = u2.uuid
            where (t.serial = $1 or t.uuid = $2) and
                t.inputer <> $3 and action = 3", [
            parseInt(req.params.id) or 0, req.params.id,
            req.session.userinfo.uuid
        ], defer err, result
    if err then done(client); return next(err)
    done()

    if result.rows.length == 0
        return next(new Error('未找到终端记录'))

    record = result.rows[0]
    res.render 'terms/check3', record: record


check_precheck = (req, res, next) ->
    for k in ['serial', 'uuid', 'input_time']
        if not req.body[k]
            return next(new Error("请求缺少参数[#{k}]"))

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query "select t.*, u.realname as input_user,
            u2.realname as check_user
        from pay_terms_ex as t
        left join web_users as u on u.uuid = t.inputer
        left join web_users as u2 on u2.uuid = t.checker
        where t.serial = $1 and t.uuid = $2", [
            req.body.serial, req.body.uuid
        ], defer err, result
    if err then done(client); return next(err)
    done()

    if result.rows.length == 0
        return next(new Error('未查询到待复核记录'))

    # 期间有可能其他用户进行了复核
    record = result.rows[0]
    if record.checked and record.checker != req.session.userinfo.uuid
        return next new Error('用户 #{record.check_user} 已经复核了此记录')

    # 期间有可能用户修改了信息
    if record.input_time.isAfter req.body.input_time, 'second'
        return next new Error("""
            在复核期间用户 #{record.input_user} 修改了原终端信息，请刷新后重新复核
        """)

    # 更新系统的通知修改时间，用户下次访问时，将重新查询通知消息
    req.app.set 'notify_modified_time', moment().format('x')
    next()


router.post '/check/pass', check_precheck, (req, res, next) ->
    if not req.body.effect_time
        return next new Error('请输入信息生效时间')

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query \
        "update pay_terms_ex set checked = true, checker = $1,
            check_time = CURRENT_TIMESTAMP, effect_time = $2,
            rejected = false, reject_reason = ''
            where serial = $3 and uuid = $4", [
            req.session.userinfo.uuid,
            req.body.effect_time,
            req.body.serial, req.body.uuid
        ], defer err, result
    if err then done(client); return next(err)
    done()

    # 增加调度器，在将来某个时间让商户生效
    jobs.sched_effect req.body.uuid, req.body.effect_time
    res.redirect '/terms'


router.post '/check/reject', check_precheck, (req, res, next) ->
    if not req.body.reject_reason or req.body.reject_reason.length == 0
        return next new Error('请输入拒绝原因')

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query \
        "update pay_terms_ex set checked = false, checker = $1,
            check_time = CURRENT_TIMESTAMP,
            rejected = true, reject_reason = $2
            where serial = $3 and uuid = $4", [
            req.session.userinfo.uuid,
            req.body.reject_reason.trim(),
            req.body.serial, req.body.uuid
        ], defer err, result
    if err then done(client); return next(err)
    done()
    res.redirect '/terms'


router.get '/batch/edit', (req, res, next) ->
    res.locals.breadcrumbs.push 'title': '批量修改'
    res.render 'terms/batch_edit', record: {}


router.post '/batch/edit', (req, res, next) ->
    res.type('json')
    res.json succ: true, redirect: '/terms'


router.post '/batch/check', (req, res, next) ->
    res.type('json')
    for k in ['records']
        return next(new Error("请求缺少参数[#{k}]")) if not req.body[k]

    records = JSON.parse req.body.records
    if records.length == 0
        return succ: true, redirect: '/terms'

    params = []
    params.push req.session.userinfo.uuid

    where_clause = "uuid in ("
    for item, index in records
        where_clause += "$#{index + 2}"
        if index < records.length - 1
            where_clause += ", "
        params.push item.uuid
    where_clause += ")"

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query \
        "update pay_terms_ex set
            checked = true, checker = $1,
            check_time = CURRENT_TIMESTAMP,
            effect_time = CURRENT_TIMESTAMP,
            rejected = false, reject_reason = ''
            where #{where_clause}",
        params, defer err, result
    if err then done(client); return next(err)
    done()

    # 增加调度器，在将来某个时间让商户生效
    for item in records
        jobs.sched_effect item.uuid, moment()

    # 更新系统的通知修改时间，用户下次访问时，将重新查询通知消息
    req.app.set 'notify_modified_time', moment().format('x')
    res.json succ: true, redirect: '/terms'


router.post '/batch/disable', (req, res, next) ->
    res.type('json')
    for k in ['records']
        return next(new Error("请求缺少参数[#{k}]")) if not req.body[k]

    records = JSON.parse req.body.records
    if records.length == 0
        return succ: true, redirect: '/terms'

    params = []
    where_clause = "uuid in ("
    for item, index in records
        where_clause += "$#{index + 1}"
        if index < records.length - 1
            where_clause += ", "
        params.push item.uuid
    where_clause += ")"

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query \
        "update pay_terms set disabled = true where #{where_clause}",
        params, defer err, result
    if err then done(client); return next(err)
    done()

    # 更新系统的通知修改时间，用户下次访问时，将重新查询通知消息
    req.app.set 'notify_modified_time', moment().format('x')
    res.json succ: true, redirect: '/terms'


router.post '/batch/enable', (req, res, next) ->
    res.type('json')
    for k in ['records']
        return next(new Error("请求缺少参数[#{k}]")) if not req.body[k]

    records = JSON.parse req.body.records
    if records.length == 0
        return succ: true, redirect: '/terms'

    params = []
    where_clause = "uuid in ("
    for item, index in records
        where_clause += "$#{index + 1}"
        if index < records.length - 1
            where_clause += ", "
        params.push item.uuid
    where_clause += ")"

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query \
        "update pay_terms set disabled = false
            where #{where_clause}", params, defer err, result
    if err then done(client); return next(err)
    done()

    # 更新系统的通知修改时间，用户下次访问时，将重新查询通知消息
    req.app.set 'notify_modified_time', moment().format('x')
    res.json succ: true, redirect: '/terms'


router.post '/batch/delete', (req, res, next) ->
    res.type('json')
    for k in ['records']
        return next(new Error("请求缺少参数[#{k}]")) if not req.body[k]

    records = JSON.parse req.body.records

    uuids = "("
    for item, index in records
        uuids += "'#{item.uuid}'"
        if index < records.length - 1
            uuids += ", "
    uuids += ")"

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    # 如果有其它终端引用了此记录，则禁止删除
    await client.query \
        "select * from pay_terms where mapto_term in #{uuids}",
        defer err, result
    if err then done(client); return next(err)
    if result.rows.length > 0
        done()
        return next new Error("""
            记录中存在正被其它终端引用的记录，在解除引用关系之前不允许删除""")

    # 如果待复核表中有其它终端引用了此记录，则禁止删除
    await client.query \
        "select * from pay_terms_ex where mapto_term in #{uuids}",
        defer err, result
    if err then done(client); return next(err)
    if result.rows.length > 0
        done()
        return next new Error("""
            记录中存在正被待复核表中的其它终端引用的记录，在解除引用关系之前不允许删除""")

    # 如果此记录存在待复核的条目，则禁止删除
    await client.query "select * from pay_terms_ex
        where ref_uuid in #{uuids}", defer err, result
    if err then done(client); return next(err)
    if result.rows.length > 0
        done()
        return next new Error("""
            记录中有被用户修改后尚未生效的记录，在新信息生效前不允许删除""")

    await client.query "insert into pay_terms_ex (
            uuid,           ctime,
            termid,         merid,
            mapto_term,
            tmk,            macalgo,
            notes,
            ref_uuid,       ref_serial,
            inputer,        input_time,     action
        ) ( select
            md5(random()::text || clock_timestamp()::text)::uuid::varchar, ctime,
            termid,         merid,
            mapto_term,
            tmk,            macalgo,
            notes,
            uuid,           serial,
            $1,             CURRENT_TIMESTAMP, 3
            from pay_terms where uuid in #{uuids}
        )", [ req.session.userinfo.uuid ], defer err, result
    if err then done(client); return next(err)
    done()

    # 更新系统的通知修改时间，用户下次访问时，将重新查询通知消息
    req.app.set 'notify_modified_time', moment().format('x')
    res.json succ: true, redirect: '/terms'


router.post '/batch/delete_ex', (req, res, next) ->
    res.type('json')
    for k in ['records']
        return next new Error("请求缺少参数[#{k}]") if not req.body[k]

    records = JSON.parse req.body.records

    uuids = "("
    for item, index in records
        uuids += "'#{item.uuid}'"
        if index < records.length - 1
            uuids += ", "
    uuids += ")"

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query \
        "select ref_uuid from pay_terms_ex where
            inputer = $1 and uuid in #{uuids}", [
            req.session.userinfo.uuid
        ], defer err, result
    if err then done(client); return next(err)

    ref_uuids = "("
    for item, index in result.rows
        ref_uuids += "'#{item.ref_uuid}'"
        if index < records.length - 1
            ref_uuids += ", "
    ref_uuids += ")"

    await client.query 'begin'

    await client.query \
        "delete from pay_terms_ex where checked = false and
            inputer = $1 and uuid in #{uuids}", [
            req.session.userinfo.uuid
        ], defer err, result
    if err then done(client); return next(err)

    await client.query \
        "delete from pay_terms where shaded = true and
            inputer = $1 and uuid in #{ref_uuids}", [
            req.session.userinfo.uuid
        ], defer err, result
    if err then done(client); return next(err)

    await client.query 'commit'
    done()

    # 更新系统的通知修改时间，用户下次访问时，将重新查询通知消息
    req.app.set 'notify_modified_time', moment().format('x')
    res.json succ: true, redirect: '/terms'


router.get '/import', (req, res, next) ->
    res.locals.breadcrumbs.push 'title': '导入商户信息'
    res.render 'terms/import', record: {}


uploader = multer(dest: settings.upload_path)
router.post '/import', uploader.single('attach_file'), (req, res, next) ->
    for k in ['inside', 'chnl']
        if not req.body[k]
            return next(new Error("请求缺少参数[#{k}]"))
    if not req.file
        return next(new Error("请求缺少上传文件."))

    # 检查文件类型是否为文本文件
    magic = new mmm.Magic(mmm.MAGIC_MIME_TYPE)
    await magic.detectFile req.file.path, defer err, result
    if err
        fs.unlink req.file.path
        return next(new Error('不能检测文件类型，导入失败'))
    if result != 'text/plain'
        fs.unlink req.file.path
        return next(new Error('上传的文件格式非文本文件，无效'))

    # 将上传文件保存到指定的目录待后续处理
    if req.body.inside is 'true'
        dest_path = "#{settings.upload_path}/terms/#{req.body.chnl}/inner"
    else
        dest_path = "#{settings.upload_path}/terms/#{req.body.chnl}/outer"
    mkdirp.sync(dest_path)
    today = moment().format('YYYY-MM-DD')
    fs.rename req.file.path, "#{dest_path}/#{today}.txt"
    res.redirect '/terms'


router.get '/export', (req, res, next) ->
    res.locals.breadcrumbs.push 'title': '导出终端信息'

    res.locals.keyword = req.query.keyword

    where_clause = "(
        t.termid like $1 or t.tmk like $1 or t.notes like $1 or
        m.merno like $1 or m.name like $1
    ) and t.shaded = false and t.disabled = false"

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query \
        "select count(t.*) as count
            from pay_terms as t
            left join pay_merch as m on m.uuid = t.merid
        where #{ where_clause }", [
            '%' + res.locals.keyword + '%'
        ], defer err, result
    if err then done(client); return next(err)
    res.locals.export_count = result.rows[0].count

    await client.query \
        "select count(t.*) as count
            from pay_terms as t
            left join pay_merch as m on m.uuid = t.merid
        where #{ where_clause } and m.inside = true", [
            '%' + res.locals.keyword + '%'
        ], defer err, result
    if err then done(client); return next(err)
    res.locals.inside_count = result.rows[0].count

    await client.query \
        "select t.*, m.merno from pay_terms as t
            left join pay_merch as m on m.uuid = t.merid
        where #{ where_clause } and m.inside = true and
            (t.tmk is null or t.tmk = '')", [
            '%' + res.locals.keyword + '%'
        ], defer err, result
    if err then done(client); return next(err)
    res.locals.notmk_records = result.rows
    done()

    res.render 'terms/export', record: {}


do_encrypt = (tmk_clear, encrypt_key) ->
    cipher = crypto.createCipheriv 'des-ede', encrypt_key, new Buffer('')
    cipher.setAutoPadding false
    tmk_cipher = cipher.update tmk_clear, 'hex', 'hex'
    tmk_cipher += cipher.final 'hex'
    return tmk_cipher.toUpperCase()


export_mpos = (req, res, next, where_clause) ->
    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    # 查询母 POS 密钥分量
    await client.query "select mpos_skey1, mpos_skey2
        from pay_secure where 1 = 1", defer err, result
    if err then done(client); return next(err)

    if result.rows.length == 0
        done()
        return next new Error('尚未配置母 POS 密钥分量，请在安全配置中进行配置.')

    # 检查母 POS 密钥分量是否有效
    secure_conf = result.rows[0]
    if secure_conf.mpos_skey1.length isnt 32 or secure_conf.mpos_skey2.length isnt 32
        done()
        return next new Error('母 POS 密钥分量配置错误，请检查安全配置.')

    # 解密母 POS 密钥分量
    mpos_skey1 = secure.decryptEde(secure_conf.mpos_skey1)
    mpos_skey2 = secure.decryptEde(secure_conf.mpos_skey2)
    if not mpos_skey1 or not mpos_skey2
        done()
        return next new Error('解密母 POS 密钥分量错误.')

    # 查询所有需要导出的终端
    await client.query \
        "select m.merno as merno, t.termid, t.tmk
            from pay_terms as t
            left join pay_merch as m on m.uuid = t.merid
        where #{where_clause} and m.inside = true and m.chnl = 1
        order by merno, t.termid", [
            '%' + res.locals.keyword + '%'
        ], defer err, result
    if err then done(client); return next(err)
    done()

    if result.rows.length == 0
        return next new Error('没有可导出的记录，请检查...')

    # 计算加密时使用的密钥(由母POS密钥合成)
    encrypt_key = new Buffer(16)
    key1 = Buffer.from(mpos_skey1, 'hex')
    key2 = Buffer.from(mpos_skey2, 'hex')
    encrypt_key[i] = key1[i] ^ key2[i] for i in [0 .. key1.length - 1]

    # 保存到内存中，后续一次写入文件
    lines = []
    for record in result.rows
        if not record.tmk or record.tmk.length != 32
            return next new Error(
                "终端[#{record.merno}][#{record.termid}]没有配置终端密钥，请检查.")

        # 首先需要解密终端主密钥
        tmk = secure.decryptEde record.tmk
        if not tmk
            return next new Error(
                "解密终端[#{record.merno}][#{record.termid}]主密钥失败.")

        # 使用母 POS 密钥对终端密钥进行加密
        cipher_tmk = do_encrypt(tmk, encrypt_key) + Array(17).join(' ')
        lines.push "\"#{record.merno}\",\"#{record.termid}\"," +
            "\"0814473610   \",\"#{cipher_tmk}\",\"#{cipher_tmk}\""

    # 下载文件保存路径和文件名
    dirname = settings.dnload_path + "/terms"
    mkdirp.sync(dirname)
    today = moment().format('YYYY-MM-DD')
    dnfile = "#{dirname}/terms-keys-#{today}.txt"

    fs.writeFileSync dnfile, lines.join('\n')
    await res.download dnfile, path.basename(dnfile), defer err
    fs.unlink dnfile
    res.end()


router.post '/export', (req, res, next) ->
    for k in ['format']
        return next new Error("请求缺少参数[#{k}]") if not req.body[k]

    res.locals.keyword = req.body.keyword

    where_clause = "(
        t.termid like $1 or t.tmk like $1 or t.notes like $1 or
        m.merno like $1 or m.name like $1
    ) and t.shaded = false and t.disabled = false"

    # 导出母 POS 灌装密钥文件
    if req.body.format == 'mpos'
        return export_mpos req, res, next, where_clause

    return next new Error('导出文件格式无效.')


# 查询可用的外联终端列表，用在编辑时
router.post '/query/mapto_terms', (req, res, next) ->
    res.type('json')
    for k in ['chnl']
        if not req.body[k]
            return next(new Error("请求缺少参数[#{k}]"))

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query \
        "select t.uuid, t.termid, t.shaded,
            m.name as mer_name, m.merno as mer_no,
            '#{constant.merch_chnls[req.body.chnl]}' as mer_chnl
            from pay_terms as t
            left join pay_merch as m on m.uuid = t.merid
        where
            t.disabled = false and
            m.inside = false and m.chnl = $1", [
            req.body.chnl
        ], defer err, result
    if err then done(client); return next(err)
    done()
    res.json succ: true, records: result.rows


# 动态查询外联终端的商户信息，用在主页展示详细信息时查询
router.post '/query/mapto_term_merch', (req, res, next) ->
    res.type('json')

    for k in ['mapto_term']
        if not req.body[k]
            return next(new Error("请求缺少参数[#{k}]"))

    await pgsql.connect settings.pgsql_url, defer err, client, done
    if err then done(client); return next(err)

    await client.query \
        "select t.termid,
            m.name as mer_name, m.merno as mer_no,
            m.inside as mer_inside, m.chnl as mer_chnl
            from pay_terms as t
            left join pay_merch as m on m.uuid = t.merid
        where
            t.disabled = false and t.uuid = $1", [
            req.body.mapto_term
        ], defer err, result
    if err then done(client); return next(err)
    done()

    record = result.rows[0]
    if not record
        return res.json succ: false, errmsg: '未查询到商户信息'

    record.mer_chnl = constant.merch_chnls[record.mer_chnl]
    res.json succ: true, record: record
